"""
ArcBall.py -- Math utilities, vector, matrix types and ArcBall quaternion rotation class
"""

from math import sqrt
from fudge.types import *

# assuming IEEE-754(GLfloat), which i believe has max precision of 7 bits
Epsilon = 1.0e-5


class ArcBall:
	def __init__ (self, NewWidth, NewHeight):
		self.m_StVec = Vector3()
		self.m_EnVec = Vector3()
		self.m_AdjustWidth = 1.0
		self.m_AdjustHeight = 1.0
		self.setBounds (NewWidth, NewHeight)

	def __str__ (self):
		return "ArcBall"

	def setBounds(self, NewWidth, NewHeight):
		# Set new bounds
		assert (NewWidth > 1.0 and NewHeight > 1.0), "Invalid width or height for bounds."
		# Set adjustment factor for width/height
		self.m_AdjustWidth = 1.0 / ((NewWidth - 1.0) * 0.5)
		self.m_AdjustHeight = 1.0 / ((NewHeight - 1.0) * 0.5)

	def _mapToSphere (self, NewPt):
		# Given a new window coordinate, will modify NewVec in place

		TempPt = NewPt.copy()
		# Adjust point coords and scale down to range of [-1 ... 1]
		TempPt.x = (NewPt.x * self.m_AdjustWidth) - 1.0
		TempPt.y = 1.0 - (NewPt.y * self.m_AdjustHeight)
		# Compute the square of the length of the vector to the point from the center
		length = TempPt.dot(TempPt)
		# If the point is mapped outside of the sphere... (length > radius squared)
		if (length > 1.0):
			norm    = 1.0 / sqrt(length)

			# //Return the "normalized" vector, a point on the sphere
			TempPt.x = TempPt.x * norm
			TempPt.y = TempPt.y * norm
			TempPt.z = 0.0;
		else:			# //Else it's on the inside
			# //Return a vector to a point mapped inside the sphere sqrt(radius squared - length)
			TempPt.z = sqrt (1.0 - length)

		return TempPt

	def click (self, NewPt):
		# //Mouse down (Point2fT
		self.m_StVec = self._mapToSphere(NewPt)
		return

	def drag (self, NewPt):
		# //Mouse drag, calculate rotation (Point2fT Quat4fT)
		""" drag (Point2fT mouse_coord) -> new_quaternion_rotation_vec
		"""

		self.m_EnVec = self._mapToSphere(NewPt)

		# //Compute the vector perpendicular to the begin and end vectors
		# Perp = Vector3fT ()
		Perp = Vector3.Cross(self.m_StVec, self.m_EnVec);

		NewRot = Quaternion4()
		# //Compute the length of the perpendicular vector
		if (Perp.length() > Epsilon):		#    //if its non-zero
			# //We're ok, so return the perpendicular vector as the transform after all
			NewRot.x = Perp.x;
			NewRot.y = Perp.y;
			NewRot.z = Perp.z;
			# //In the quaternion values, w is cosine (theta / 2), where theta is rotation angle
			NewRot.w = self.m_StVec.dot(self.m_EnVec);
		else:		#                            //if its zero
			# //The begin and end vectors coincide, so return a quaternion of zero matrix (no rotation)
			NewRot.x = NewRot.y = NewRot.z = NewRot.w = 0.0;
			
		return NewRot